/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * assign2
 * Copyright (C)  Michael Morckos 2008 <mikey.morckos@gmail.com>
 * 
 * assign2 is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * assign2 is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "assign2.h"

/* WRAPPER FOR IMAGE DATA */
template <class T> class IMG {
	
	public:
        IMG(IplImage* img = 0) {
			
			imgp = img;
		}
		
        ~IMG() {
			
			imgp = 0;
		}
        
		void operator=(IplImage* img) {
			
			imgp = img;
		}
		
        inline T* operator[](const int rowIndx) {
            
			return ((T *)(imgp->imageData + rowIndx*imgp->widthStep));
		}
	
    private:
        IplImage* imgp;
};

class RgbPixel {
    
	public:
	    unsigned char b,g,r;
};

class RgbPixelFloat {
	
	public:
        float b, g, r;
};


typedef IMG <RgbPixel>  MY_RgbImage;
typedef IMG <RgbPixelFloat>  MY_RgbFloat;

typedef IMG <unsigned char>  MY_GrayImage;

const char *name1 = "Original", *name2 = "HSV", *name3 = "Stretched Image", *name4 = "Back to RGB";

/* INSTRUCTOR */
Img_Editor :: Img_Editor () 

: load_button("Load Image"),

  radio_1_1("RGB"),
  radio_1_2("HSV"),
  button_1_1("Convert"),
  button_1_6("Equalize"),

  radio_1_3("Histogram"),
  radio_1_4("Cummulative Histogram"),
  button_1_2("Plot"),
  button_1_3("Plot R"),
  button_1_4("Plot G"),
  button_1_5("Plot B"),
  button_2_1("Plot"),
  button_2_2("Stretch"),

  label1("A :"),
  label2("B :"),

//  aj_1(0.0, 0.0, 255.0, 1.0, 1.0, 0.0),
//  aj_2(255.0, 0.0, 255.0, 1.0, 1.0, 0.0),

  //entry1(aj_1),
  //entry2(aj_2),

  frame1("Histogram Equalization"),
  frame2("Contrast Stretching"),
  main_hbox(false, 5)

{
  // Sets the border width of the window.
    set_border_width(10);
	set_title("Image Editor");
	
	add(main_hbox);
	
    main_hbox.set_border_width(20);
	main_hbox.pack_start(main_vbox_1);
	main_hbox.pack_start(main_vbox_2);
	
	load_button.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: loadImage));
    
	main_vbox_1.pack_start(load_button);
    main_vbox_1.pack_start(frame1, PACK_EXPAND_WIDGET, 20);
	main_vbox_1.pack_start(frame2, PACK_EXPAND_WIDGET, 20);
	
	/* Frame 1 */
	frame1.add(frame1_hbox);
	frame1_hbox.pack_start(frame1_vbox_1_1, PACK_EXPAND_WIDGET, 10);
	frame1_hbox.pack_start(frame1_vbox_1_2, PACK_EXPAND_WIDGET, 10);
	
	RadioButton::Group group = radio_1_1.get_group();
	radio_1_2.set_group(group);
	frame1_vbox_1_1.pack_start(radio_1_1, PACK_EXPAND_WIDGET, 5);
	frame1_vbox_1_1.pack_start(radio_1_2, PACK_EXPAND_WIDGET, 5);
	frame1_vbox_1_1.pack_start(button_1_1, PACK_EXPAND_WIDGET, 10);
	frame1_vbox_1_1.pack_start(button_1_6);
	
	group = radio_1_3.get_group();
	radio_1_4.set_group(group);
	frame1_vbox_1_2.pack_start(radio_1_3, PACK_EXPAND_WIDGET, 5);
	frame1_vbox_1_2.pack_start(radio_1_4, PACK_EXPAND_WIDGET, 5);
	
	hbox.pack_start(button_1_2, PACK_EXPAND_WIDGET, 5);
	hbox.pack_start(button_1_3, PACK_EXPAND_WIDGET, 5);
	hbox.pack_start(button_1_4, PACK_EXPAND_WIDGET, 5);
	hbox.pack_start(button_1_5, PACK_EXPAND_WIDGET, 5);
	
	frame1_vbox_1_2.pack_start(hbox, PACK_EXPAND_WIDGET, 10);
	
	button_1_1.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: convert));
	button_1_6.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: equalize));
	button_1_2.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: plot));
	button_1_3.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: plotR));
	button_1_4.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: plotG));
	button_1_5.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: plotB));
	/******/
	
	/* Frame 2 */
	frame2.add(frame2_vbox);
	frame2_vbox.pack_start(frame2_hbox_2_1, PACK_EXPAND_WIDGET, 5);
	frame2_vbox.pack_start(frame2_hbox_2_2, PACK_EXPAND_WIDGET, 5);
	hbox2.pack_start(button_2_1, PACK_EXPAND_WIDGET, 5);
	hbox2.pack_start(button_2_2, PACK_EXPAND_WIDGET, 5);
	frame2_vbox.pack_start(hbox2, PACK_EXPAND_WIDGET, 5);
	
	//entry1.set_wrap();
	//entry2.set_wrap();
	
	frame2_hbox_2_1.pack_start(label1);
	frame2_hbox_2_1.pack_start(entry1, PACK_EXPAND_WIDGET, 5);
	
	frame2_hbox_2_2.pack_start(label2);
	frame2_hbox_2_2.pack_start(entry2, PACK_EXPAND_WIDGET, 5);
	
	button_2_1.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: plotSingle));
	button_2_2.signal_clicked().connect(sigc :: mem_fun(*this, &Img_Editor :: stretch));
	/******/
	
	show_all_children();
	
	/* IMAGE DATA */
	toggleRGB = TRUE;
	toggleStretch = FALSE;
	R = new Intensity [256];
	G = new Intensity [256];
	B = new Intensity [256];
	
	H = new Intensity [360];
	S = new Intensity [100];
	V = new Intensity [100];
	
	gray = new Intensity[256];
	
	/* openCV */
	file_name = 0;
	toggleStretch = FALSE;
	toggleRGB = TRUE;
}

/* DESTRUCTOR */
Img_Editor::~Img_Editor () {

    delete [] R;
	delete [] G;
	delete [] B;
	
	delete [] H;
	delete [] S;
	delete [] V;
	
	delete [] gray;
}

/* LOADING IMAGE */
void Img_Editor :: loadImage () {
	
	Gtk::FileChooserDialog dialog("Please choose an image", FILE_CHOOSER_ACTION_OPEN);
    dialog.set_transient_for(*this);

    dialog.add_button(Gtk::Stock::CANCEL, RESPONSE_CANCEL);
    dialog.add_button(Gtk::Stock::OPEN, RESPONSE_OK);

 //   Gtk::FileFilter filter_any;
 //   filter_any.set_name("All files");
 //   filter_any.add_pattern("*");
 //   dialog.add_filter(filter_any);

    int result = dialog.run();

    switch(result) {
        case(RESPONSE_OK) : {
            file_name = (dialog.get_filename()).c_str();
            cout << "File selected: " <<  file_name << endl;
			displayImage();
            break;
        }
        
		case(RESPONSE_CANCEL) : break;
        
		default: break;
    }
}

/* IMAGE DISPLAY */
void Img_Editor :: displayImage () {
	
	if(file_name) {
		img1 = cvLoadImage (file_name, 3);
		
		if(!img1) 
		    cout <<  "Could not load image : " << file_name << endl;
		else {
			toggleStretch = FALSE;
			toggleRGB = TRUE;
			cvShowImage(name1, img1);
			
			cvDestroyWindow(name2);
			cvDestroyWindow(name3);
			cvDestroyWindow(name4);
		}	
	}
	else
		cout <<  "No file chosen!" << endl;
	
}
/* RGB / HSV DATA EXTRACTION FOR GRAPH PLOTTING */
bool Img_Editor :: extract (int type, int img_num) {
	
	Intensity *temp1 = R, *temp2 = G, *temp3 = B, *temp4 = V;
	int R_num = 0, G_num = 0, B_num = 0;
	
	IplImage* img_temp = 0;
	
	if(type == 1 && img1)
	    img_temp = img1;
	
	else if(type == 2 && img2)
		img_temp = img2;
	
	else {
		cout << "No image loaded." << endl;
		return FALSE;
	}	
	
	MY_RgbImage img_data(img_temp);
	
	/* RGB */
	if(type == 1) {
		/* RGB */
		if(toggleRGB) {
			/* RED CHANNEL*/
			for(int i = 0; i < 256; i++) { 
				R_num = 0, G_num = 0, B_num = 0;
				
				temp1->setVal(i);
				temp2->setVal(i);
				temp3->setVal(i);
				
				for(int j = 0; j < img1->width; j++) {
				    for(int k = 0; k < img1->height; k++) {
					    if(((int)img_data[j][k].r) == i)
							R_num++;
						
						if(((int)img_data[j][k].g) == i)
							G_num++;
						
						if(((int)img_data[j][k].b) == i)
							B_num++;
					}	
				}
				temp1->setAmpl(R_num);
				temp1++;
				
				temp2->setAmpl(G_num);
				temp2++;
				
				temp3->setAmpl(B_num);
				temp3++;
			}
		}
		
		return TRUE;
	}
	/* HSV */
	else {
		CvScalar s;
		float x;
		
		for(int i = 0; i < 256; i++) { 
			R_num = 0;
				
			temp4->setVal(i);

			for(int j = 0; j < img2->height; j++) {
				for(int k = 0; k < img2->height; k++) {    
					s = cvGet2D(img2, j, k);
					x = (255 * s.val[1]);
					if((int)x == i)
						R_num++;						
				}	
			}
			temp4->setAmpl(R_num);
			temp4++;
		}
		
		return TRUE;
	}
	
	return FALSE;
}

void Img_Editor :: convert() {
	
	if(radio_1_2.get_active() && toggleRGB == TRUE)
        convert_RGB_HSV();
	
	else if(radio_1_1.get_active() && toggleRGB == FALSE)
		convert_HSV_RGB();
}	

/******************************************************************/
//CvScalar s1, s2, s3, s4, s5, s6;
//CvScalar or1, or2, or3;

/* RGB-->HSV CONVERSION */
void Img_Editor :: convert_RGB_HSV() {
/* 
	s1 = cvGet2D(img1, 10, 100);
	s1.val[1] = cvGet2D(img1, 0, 1);
	s1.val[0] = cvGet2D(img1, 0, 2);
	
	s2 = cvGet2D(img1, 150, 12);
	s2.val[1] = cvGet2D(img1, 1, 1);
	s2.val[0] = cvGet2D(img1, 1, 2);
	
	s3 = cvGet2D(img1, 0, 0);

	or1 = s1; or2 = s2; or3 = s3;
	
	s4 = convert_RGB_HSV_helper(s1);
	s5 = convert_RGB_HSV_helper(s2);
	s6 = convert_RGB_HSV_helper(s3);
	
    cout << "RGB1 " << s1.val[2] << " " << s1.val[1] << " " << s1.val[0] << " **HSV " << s4.val[2] << " " << s4.val[1] << " " << s4.val[0] << endl;	
	cout << "RGB1 " << s2.val[2] << " " << s2.val[1] << " " << s2.val[0] << " **HSV " << s5.val[2] << " " << s5.val[1] << " " << s5.val[0] << endl;
	cout << "RGB " << s3.val[2] << " " << s3.val[1] << " " << s3.val[0] << " **HSV " << s6.val[2] << " " << s6.val[1] << " " << s6.val[0] << endl;
*/
	img2 = cvCreateImage(cvSize(img1->width, img1->height), IPL_DEPTH_32F, 3);
	
	CvScalar s;
	
	for(int i = 0; i < img1->height; i++) {
		for(int j = 0; j < img1->width; j++) {
			s = convert_RGB_HSV_helper(cvGet2D(img1, i, j));			
			cvSet2D(img2, i, j, s);
		}
	}	
		
	cvNamedWindow(name2, 6);
	cvShowImage(name2, img2);
	toggleRGB = FALSE;
}

/* RGB-->HSV CONVERSION HELPER */
CvScalar Img_Editor :: convert_RGB_HSV_helper(CvScalar s) {
	
	double R = s.val[2] / 255.0;
	double G = s.val[1] / 255.0;
	double B = s.val[0] / 255.0;
	
	double constant = 60.0, maxi, mini;	
	
	maxi = max(R, G, B);
	mini = min(R, G, B);
	
	int l = (0.5) * (maxi+mini);
				
	/* H CHANNEL */
	if(maxi == mini) 
		s.val[2] = 0.0;
				
	else if(R == maxi && G >= B)
		s.val[2] = (constant * ((G - B) / (maxi - mini)));
				
	else if(R == maxi && G < B)
		s.val[2] = (constant * ((G - B) / (maxi - mini))) + 360.0;
		
	else if(G == maxi)
		s.val[2] = (constant * ((B - R) / (maxi - mini))) + 120.0;
				
	else if(B == maxi)
		s.val[2] = (constant * ((R - G) / (maxi - mini))) + 240.0;
				
	/* S CHANNEL */
	if(maxi == 0.0)
		s.val[1] = 0.0;
	
	else 
		s.val[1] = 1 - (mini/maxi);
	
	/* V channel */
	s.val[0] = maxi;
				
	return s;  
}

/* HSV-->RGB CONVERSION */
void Img_Editor :: convert_HSV_RGB () {

/*
	s1 = convert_HSV_RGB_helper(s4);
	s2 = convert_HSV_RGB_helper(s5);
	s3 = convert_HSV_RGB_helper(s6);
	
	
	cout << "HSV2 " << s4.val[2] << " " << s4.val[1] << " " << s4.val[0] << " **RGB " << s1.val[2] << " " << s1.val[1] << " " << s1.val[0] << endl;	
	cout << "HSV2 " << s5.val[2] << " " << s5.val[1] << " " << s5.val[0] << " **RGB " << s2.val[2] << " " << s2.val[1] << " " << s2.val[0] << endl;
	cout << "HSV2 " << s6.val[2] << " " << s6.val[1] << " " << s6.val[0] << " **RGB " << s3.val[2] << " " << s3.val[1] << " " << s3.val[0] << endl;
*/
		
//	img2 = cvCloneImage(img1);
//	cvDestroyWindow(name1);
	
	CvScalar s;
	
	for(int i = 0; i < img2->height; i++) {	
		for(int j = 0; j < img2->width; j++) {
			s = convert_HSV_RGB_helper(cvGet2D(img2, i, j));
			cvSet2D(img1, i, j, s);
		}
	}	
		
	cvNamedWindow(name4, 6);
	cvShowImage(name4, img1);
	toggleRGB = TRUE;
}

/* RGB-->HSV CONVERSION HELPER */
CvScalar Img_Editor :: convert_HSV_RGB_helper (CvScalar s) {
	
	double H = s.val[2];
	double S = s.val[1];
	double V = s.val[0];
	
	int Hi = H / 60;
	double f = (H / 60) - Hi;
	double p = V * (1 - S);
	double q = V * (1 - (f * S));
	double t = V * (1 - ((1 - f) * S));
	
	switch(Hi) {
		case 0 : s.val[2] = V;
		         s.val[1] = t;
		         s.val[0] = p; break;

		case 1 : s.val[2] = q;
		         s.val[1] = V;
		         s.val[0] = p; break;
		
		case 2 : s.val[2] = p;
		         s.val[1] = V;
		         s.val[0] = t; break;
		
		case 3 : s.val[2] = p;
		         s.val[1] = q;
		         s.val[0] = V; break;
		
		case 4 : s.val[2] = t;
		         s.val[1] = p;
		         s.val[0] = V; break;
		
		case 5 : s.val[2] = V;
		         s.val[1] = p;
		         s.val[0] = q; break;
	}	
	
	s.val[2] = s.val[2] * 255;
	s.val[1] = s.val[1] * 255;
	s.val[0] = s.val[0] * 255;
	
	return s;  
}

/* HISTOGRAM EQUALIZATION */
void Img_Editor :: equalize() {
	
	CvScalar s;
	
	Intensity* temp;
	int cummu; 
	double result, current_channel_pix; 
	int num_of_pixs = img2->height * img2->width;
	float constant = 255 / num_of_pixs;
	float x;
	
	if(extract (2, 2)) {
		temp = V;
		
		for(int i = 0; i < 256; i++) {
			cummu += temp->getAmpl();
			result = cummu * constant;
			current_channel_pix = temp->getVal();
			
			for(int j = 0; j < img2->height; j++) {
				for(int k = 0; k < img2->width; k++) {
					s = cvGet2D(img2, j, k);
					x = (255 * s.val[1]);
					if(current_channel_pix == (int)x) {
						s.val[0] = result;
					    break;	
					}	
					
					cvSet2D(img2, j, k, s);
				}
			}
			temp++;
		}
	}	
}	


/************** GRAPH PLOTTING FOR RGB AND HSV **************/

/* RGB / HSV GRAPH PLOTTING */
void Img_Editor :: plot () {
	
	bool flag;
	
	if(toggleRGB)
		flag = extract (1, 1);
	else
	    flag = extract (2, 2);
	
	if(flag) {
		Gtk::Window win;
		
		if(toggleRGB) {  
			win.set_title("RGB");
			Graph_Plotter area(R, G, B);
			if(radio_1_4.get_active())
			area.setType(1);
		    win.add(area);
            area.show();
			
			Gtk::Main::run(win);
	    }
		else {
			win.set_title("HSV - V Channel");
			Graph_Plotter area(V, -1);
			if(radio_1_4.get_active())
			area.setType(1);
			
		    win.add(area);
            area.show();
			
			Gtk::Main::run(win);
		}
	}	
}

void Img_Editor :: plotR () {
	
	if(extract (1, 1)) {
		Gtk::Window win;
        win.set_title("R Channel");

        Graph_Plotter area(R, 0);
		
		if(radio_1_4.get_active())
			area.setType(1);
		
        win.add(area);
        area.show();

        Gtk::Main::run(win);
	}	
}

void Img_Editor :: plotG () {
	
	if(extract (1, 1)) {
		Gtk::Window win;
        win.set_title("G Channel");

        Graph_Plotter area(G, 1);
		if(radio_1_4.get_active())
			area.setType(1);
		
        win.add(area);
        area.show();

        Gtk::Main::run(win);
	}
}

void Img_Editor :: plotB () {
	
	if(extract (1, 1)) {
		Gtk::Window win;
        win.set_title("B Channel");

        Graph_Plotter area(B, 2);
		if(radio_1_4.get_active())
			area.setType(1);
		
        win.add(area);
        area.show();

        Gtk::Main::run(win);
	}
}

/************** GRAYSCALE **************/

/* GRAYSCALE DATA EXTRACTION */
bool Img_Editor :: extract_gray(int img_num) {
	
	Intensity* temp = gray;
	CvScalar s;
	
	IplImage* img_gray_temp = 0;
	
	int R_num = 0;
	
	if(img_num == 1 && img1)
	    img_gray_temp = img1;
	
	else if(img_num == 2 && img2)
		img_gray_temp = img2;
	
	else {
		cout << "No image loaded." << endl;
		return FALSE;
	}
		
	for(int i = 0; i < 256; i++) {    
		R_num = 0;
				
		temp->setVal(i);
				
		for(int j = 0; j < img_gray_temp->height; j++) {	
	        for(int k = 0; k < img_gray_temp->width; k++) {
				s = cvGet2D(img_gray_temp, j, k);
				
				if(((int)s.val[0]) == i)
					R_num++;
			}	
		}
		
		temp->setAmpl(R_num);
		temp++;
	}
	return TRUE;
}	

/* PLOTTING SINGLE GRAYSCALE CHANNEL */
void Img_Editor ::  plotSingle () {
	
	bool flag;
	
	if(toggleStretch)
	    flag  = extract_gray(2);
	else
		flag  = extract_gray(1);
		
	if(flag) {
		Gtk::Window win;
        win.set_title("Gray Scale Histogram");

        Graph_Plotter area(gray, -1);
        win.add(area);
        area.show();

        Gtk::Main::run(win);
	}
}	

/* CONTRAST STRETCHING */
void Img_Editor :: stretch () {
	
	int a, b, c, d;
	int width, height;
	int scaling_factor, result;
	CvScalar s1, s2;
	
	a = atoi((entry1.get_text()).c_str());
	b = atoi((entry2.get_text()).c_str());
		
	bool flag = TRUE;
	
	/* EXTRACTING DATA OF THE ORIGINAL IMAGE */
	if(!extract_gray(1)) {
		cout << "No image loaded!" << endl;
		return;
	}
	
	Intensity* temp = gray;
	
	/* DETERMINING THE C & D EDGES */
	for(int i = 0; i < 255; i++) {
		if(flag) {
		    if(temp->getAmpl() > 0) { 
			    c = i;
		        flag = FALSE;
		    }
		}
		temp++;
	}
	
	for(int i = 255; i >= 0; i--) {
		if(temp->getAmpl() > 0) { 
	        d = i;
		   break;
		}
		temp--;
	}
	
	/* CREATING THE NEW IMAGE */
	img2 = cvCloneImage(img1);
	
	/* SCALING FACTOR */
	scaling_factor = ((b - a) / (d - c));
	
	width = img1->width;
	height = img1->height;
	
	for(int i = 0; i < height; i++) {
		for(int j = 0; j < width; j++) {
			/* STRETCHING */
			s1 = cvGet2D(img1, i, j);
			s2 = cvGet2D(img2, i, j);
		
			result = ((s1.val[0] - c) * scaling_factor) + a;
			
			s2.val[0] = result;	
			s2.val[1] = result;
			s2.val[2] = result;
			
			cvSet2D(img2, i, j, s2);
		}
	}
	
	cvNamedWindow(name3, 6);
	cvShowImage(name3, img2);
	toggleStretch = TRUE;
}

bool Img_Editor :: on_my_frame_event(GdkEvent* event) {

	cvReleaseImage (&img1);
	cvReleaseImage (&img2);
	
	cvDestroyWindow (name1);
	cvDestroyWindow (name2);
	
	gtk_main_quit ();
    return FALSE;
}

int main (int argc, char *argv[]) {

    Gtk::Main kit(argc, argv);	

    Img_Editor editor;

    /* openCV frame */
	cvNamedWindow(name1, 6);

    Gtk::Main::run(editor);
	
	cvDestroyWindow (name1);
	cvDestroyWindow (name2);

    return 0;
}

